from .utils import CfDict, RTOL, ATOL
from .utils import equals as utils_equals

# ====================================================================
#
# Transform object
#
# ====================================================================

class Transform(CfDict):
    '''

A CF transform construct.

The named parameters and their values of the transformation (i.e. the
transformation's mappings) comprise the object's key-value pairs.

A transformation is equivalent to either a CF-netCDF 'formula_terms'
or 'grid_mapping' property.   DCH DCH DCH

In the 'formula_terms' case, a mapping to a coordinate (as opposed to
another field) uses the coordinate's space key name as a pointer
rather than a copy of the coordinate itself.

**Examples**

>>> t
<CF Transform: atmosphere_sigma_coordinate>
>>> print t.dump()
atmosphere_sigma_coordinate transform
-------------------------------------
Transform['ps'] = <CF Field: surface_air_pressure(73, 96)>
Transform['ptop'] = 0.05
Transform['sigma'] = 'dim0'

>>> t
<CF Transform: rotated_latitude_longitude>
>>> print t.dump()
rotated_latitude_longitude transform
------------------------------------
Transform['grid_north_pole_latitude'] = 33.67
Transform['grid_north_pole_longitude'] = 190.0

'''

    def __init__(self, *args, **kwargs):
        '''

**Initialization**

:Parameters:

    args, kwargs :
        Keys and values are initialized exactly as for a built-in
        dict. Keys are transform parameter names (such as
        grid_north_pole_latitude or sigma) with appropriate values of
        string, numeric, Coordinate or Field types.

'''
        super(Transform, self).__init__(*args, **kwargs)
    #--- End: def


    def __hash__(self):
        '''

x.__hash__() <==> hash(x)

:Raises:

    ValueError:
        If the transform is not a grid mapping.

'''
        if not self.isgrid_mapping:
            raise ValueError("Can't hash a formula_terms transform")

        h = sorted(self.items())
        h.append(self.name)

        return hash(tuple(h))
    #--- End: def

    def __repr__(self):
        '''
x.__repr__() <==> repr(x)

''' 
        try:
            return '<CF %s: %s>' % (self.__class__.__name__, self.name)
        except AttributeError:
            return '<CF %s: >' % self.__class__.__name__
    #--- End: def

    def __str__(self):
        '''
x.__str__() <==> str(x)

'''    
        return 'Transform       : %s' % repr(self)
    #--- End: def

    @property
    def isformula_terms(self):
        '''

True if the transform is a formula_terms.

**Examples**

>>> t
<CF Transform: atmosphere_sigma_coordinate>
>>> t.isformula_terms
True
>>> t
<CF Transform: rotated_latitude_longitude>
>>> t.isformula_terms
False

'''
        return not self.isgrid_mapping
    #--- End: def

    @property
    def isgrid_mapping(self):
        '''

True if the transform is a grid_mapping.

**Examples**

>>> t
<CF Transform: rotated_latitude_longitude>
>>> t.isgrid_mapping
True
<CF Transform: atmosphere_sigma_coordinate>
>>> t.isgrid_mapping
True

'''
        return hasattr(self, 'grid_mapping')
    #--- End: def

    # ----------------------------------------------------------------
    # Attribute: name
    # ----------------------------------------------------------------
    @property
    def name(self): 
        '''

The identifying name of the transformation.

**Examples**

>>> t.name
'atmosphere_hybrid_sigma_pressure_coordinate'
>>> t.name = 'rotated_latitude_longitude'

'''        
        return self._name
    #--- End: def
    @name.setter
    def name(self, value):
        self._name = value

    def dump(self, id=None):
        '''

Return a string containing a full description of the transform.

:Parameters:

    id : str, optional
        Set the common prefix of variable component names. By default
        the instance's class name is used.

:Returns:

    out : str
        A string containing the description.

**Examples**

>>> x = t.dump()
>>> print t.dump()
>>> print t.dump(id='transform1')

'''
        if id is None:
            id = self.__class__.__name__
            
        try:
            string = ['%s transform' % self.name]
        except AttributeError:
            string = [' transform']

        string.append(''.ljust(len(string[0]), '-'))

        for key in sorted(self.keys()):
            string.append("%s['%s'] = %s" % (id, key, repr(self[key])))

        return '\n'.join(string)
    #--- End: def

    def equals(self, other, rtol=None, atol=None, traceback=False):
        '''

True if two instances are equal, False otherwise.

:Parameters:

    other : 
        The object to compare for equality.

    atol : float, optional
        The absolute tolerance for all numerical comparisons, By
        default the value returned by the `ATOL` function is used.

    rtol : float, optional
        The relative tolerance for all numerical comparisons, By
        default the value returned by the `RTOL` function is used.

    traceback : bool, optional
        If True then print a traceback highlighting where the two
        instances differ.

:Returns: 

    out : bool
        Whether or not the two instances are equal.

**Examples**

'''
        if rtol is None:
            rtol = RTOL()
        if atol is None:
            atol = ATOL()
        
        # Check the keys and values
        if not super(Transform, self).equals(other, rtol=rtol, atol=atol,
                                             traceback=traceback):
            return False
        
        # Check the name
        attr = 'name'
        x = getattr(self, attr, None)
        y = getattr(other, attr, None)
        if not utils_equals(x, y, rtol=rtol, atol=atol, traceback=traceback):
            if traceback:
                print("%s: Different '%s' attributes: %s, %s" %
                      (self.__class__.__name__, attr, x, y))
            return False
        #--- End: if
                
        # Still here?
        return True
    #--- End: def

#--- End: class
